﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

namespace Xceed.Silverlight.Data
{
  internal class LocalListEnumerator<T> : SyncEnumerator<T>
  {
    public LocalListEnumerator( List<T> dataList )
    {
      if( dataList == null )
        throw new ArgumentNullException( "dataList" );

      m_dataList = dataList;
    }

    protected virtual List<T> DataList
    {
      get
      {
        return m_dataList;
      }
    }

    private int LimitBounds( List<T> items, long index )
    {
      return Convert.ToInt32(
               Math.Max(
                 -1,
                 Math.Min( Convert.ToInt64( items.Count ), index ) ) );
    }

    public override CountResult Count( CountParameters parameters )
    {
      FilterExpressionBase filter = null;
      if( ( parameters.StartPosition != null ) && ( parameters.StartPosition.SortingReference.Count > 0 ) )
      {
        IList<object> values = parameters.StartPosition.SortValues;
        IList<SortDescription> sortOrder = parameters.StartPosition.SortingReference.Take( values.Count ).ToList();

        if( parameters.TargetGroup )
        {
          filter = NavigationFilterUtils.CreateFilterExpression( sortOrder, values );
        }
        else
        {
          bool includeStartingItem = ( parameters.StartPosition.StartPosition == StartPosition.Before );

          filter = NavigationFilterUtils.CreateFilterExpression( sortOrder, values, false, includeStartingItem );
        }
      }

      IEnumerable<T> dataSource = m_dataList;
      if( filter != null )
      {
        PredicateBuilder pb = new PredicateBuilder( filter );
        Func<T, bool> predicate = pb.ToPredicate<T>();

        dataSource = dataSource.Where( predicate );
      }

      return new CountResult( dataSource.Count() );
    }

    public override GroupCountResult GroupCount( CountParameters parameters )
    {
      Debug.Assert( false, "not expected to be used" );

      return new GroupCountResult( null );
    }

    public override StatResults GetStatValues( StatParameters parameters )
    {
      Debug.Assert( false, "not expected to be used" );

      throw new DataInternalException();
    }

    public override bool JumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      out Nullable<long> actualMoveCount,
      out ICollection<T> fetchedItems,
      out SourcePosition endPosition,
      out RelativeOffset endPositionOffset,
      out EstimateInt64 totalCount )
    {
      SourcePosition sourcePosition = parameters.SourcePosition;
      long expectedMoveCount = parameters.ExpectedMoveCount;
      int expectedFetchCount = parameters.ExpectedFetchCount;

      this.CheckMoveAndFetchParameters( expectedMoveCount, expectedFetchCount );

      List<T> dataList = this.DataList;
      int queryDirection = ( expectedMoveCount != 0 ) ? DataUtils.GetUnit( expectedMoveCount ) : DataUtils.GetUnit( expectedFetchCount );
      int sourcePositionIndex = this.FindSourcePositionIndex( sourcePosition, parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );
      int moveResult = this.MoveIndex( dataList, sourcePositionIndex, expectedMoveCount );

      actualMoveCount = moveResult - sourcePositionIndex;
      totalCount = dataList.Count;

      if( moveResult == -1 || moveResult == dataList.Count )
      {
        fetchedItems = null;
        if( moveResult == -1 )
        {
          endPosition = SourcePosition.BeforeFirst;
          endPositionOffset = new RelativeOffset( 0, ReferencePoint.BeforeFirst );
        }
        else
        {
          endPosition = SourcePosition.AfterLast;
          endPositionOffset = new RelativeOffset( 0, ReferencePoint.AfterLast );
        }
      }
      else
      {
        IList<T> fetchResult = this.Fetch( dataList, moveResult, expectedFetchCount );

        fetchedItems = fetchResult;
        Debug.Assert( fetchResult != null );
        Debug.Assert( ( fetchResult.Count == 0 ) == ( expectedFetchCount == 0 ) );

        if( fetchResult.Count != Math.Abs( expectedFetchCount ) )
        {
          if( DataUtils.GetUnit( expectedFetchCount ) == 1 )
          {
            endPosition = SourcePosition.AfterLast;
            endPositionOffset = new RelativeOffset( 0, ReferencePoint.AfterLast );
          }
          else
          {
            endPosition = SourcePosition.BeforeFirst;
            endPositionOffset = new RelativeOffset( 0, ReferencePoint.BeforeFirst );
          }
        }
        else
        {

          if( fetchResult.Count == 0 )
          {
            endPosition = new ItemPosition( dataList[ moveResult ] );
            endPositionOffset = new RelativeOffset( moveResult + 1, ReferencePoint.BeforeFirst );
          }
          else
          {
            endPosition = new ItemPosition( fetchResult[ fetchResult.Count - 1 ] );
            endPositionOffset = new RelativeOffset( dataList.IndexOf( fetchResult[ fetchResult.Count - 1 ] ) + 1, ReferencePoint.BeforeFirst );
          }
        }
      }

      return ( actualMoveCount == expectedMoveCount ) && ( fetchedItems != null ) && ( fetchedItems.Count == Math.Abs( expectedFetchCount ) );
    }

    protected virtual int FindSourcePositionIndex( SourcePosition sourcePosition, long expectedMoveCount, int expectedFetchCount )
    {
      if(sourcePosition.IsBeforeFirst)
        return -1;

      if(sourcePosition.IsAfterLast)
        return this.DataList.Count;

      if(sourcePosition is ItemPosition)
      {
        T item = (T) ((ItemPosition)sourcePosition).Item;
        int itemIndex = this.GetItemIndex(item);

        Debug.Assert(itemIndex != -1, "Inconsistent data source");
        return itemIndex;
      }

      throw new DataInternalException();
    }

    protected virtual int GetItemIndex(T item)
    {
      return this.DataList.IndexOf(item);
    }

    protected int MoveIndex( List<T> list, int initialIndex, long expectedMoveCount )
    {
      return this.LimitBounds( list, initialIndex + expectedMoveCount );
    }

    protected IList<T> Fetch( List<T> fetchedList, int firstFetchedIndex, int expectedFetchCount )
    {
      if( fetchedList == null )
        throw new ArgumentNullException( "fetchedList" );

      if( firstFetchedIndex < 0 || firstFetchedIndex >= fetchedList.Count )
        return null;

      if( expectedFetchCount == 0 )
        return new List<T>( 0 );

      int endFetchIndex = this.LimitBounds( fetchedList, firstFetchedIndex + expectedFetchCount );
      int fetchedIndex = ( firstFetchedIndex <= endFetchIndex ) ? firstFetchedIndex : endFetchIndex + 1;
      int fetchedCount = Math.Abs( endFetchIndex - firstFetchedIndex );

      List<T> fetched = fetchedList.GetRange( fetchedIndex, fetchedCount );

      if( DataUtils.GetUnit( expectedFetchCount ) == -1 )
      {
        fetched.Reverse();
      }
      return fetched;
    }

    private List<T> m_dataList;
  }
}
